package com.e0box.game.utils
{
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.PNGEncoderOptions;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.OutputProgressEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;
	import flash.utils.getTimer;
	
	import com.e0box.game.debug.Console;
	import com.e0box.game.display.BitmapFrame;
	import com.e0box.game.utils.storage.BitmapDataStorage;
	
	import utils.Random;
	
	public class Tools
	{
		public function Tools()
		{
			throw new Error("Error:can't instance the static class 'Tools'.");
		}
		/**
		 * output the message with time & getTimer
		 * 
		 * @param msg
		 * @param withTime
		 * 
		 */		
		public static function echo(msg:Object, withTime:Boolean = true, alsoAddInConsole:Boolean = true):String{
			if(msg == null) msg = "null";
			
			if(withTime){
				var time:String = new Date().toString();
				time = "[" + time.split(" ")[3] + "(" + Tools.localTimer + ")" + "]";
				
				msg = time + " " + msg.toString();
				
				trace(msg);
				
			}else trace(msg);
			
			if(alsoAddInConsole) Console.output(msg.toString());
			
			return msg.toString();
		}
		
		public static function error(msg:Object, withTime:Boolean = true):String{
			
			if(msg == null) msg = "null";
			
			if(withTime){
				var time:String = new Date().toString();
				time = "[" + time.split(" ")[3] + "(" + Tools.localTimer + ")" + "]";
				
				msg = time + " " + msg.toString();
				
				trace("Error:",msg);
				
			}else trace("Error:",msg);
			
			Console.output(msg.toString(),Console.CHANNEL_ERROR);
			
			return msg.toString();
		}
		
		/**
		 * system method getTimer()
		 * @return 
		 * 
		 */		
		public static function get localTimer():Number{
			return getTimer();
		}
		
		/**
		 * uncompress the bytes
		 * allow the ByteArray.compress() & zlib
		 * @param source
		 * @return 
		 * 
		 */		
		public static function uncompress(source:ByteArray):ByteArray{
			var bytes:ByteArray = new ByteArray();
			
			if(isCompressed(source)){
				source.position = 4;
				source.readBytes(bytes);
			}else{
				source.position = 0;
				source.readBytes(bytes);
			}
			
			try{
				bytes.uncompress();
			}catch(e:Error){
				error("compress error.");
			}
			
			return bytes;
		}
		/**
		 * check if the bytes compress in zlib
		 * @param bytes
		 * @return 
		 * 
		 */		
		public static function isCompressed(bytes:ByteArray):Boolean{
			bytes.position = 0;
			
			if(bytes.readUnsignedByte() != 0xD8) return false;
			if(bytes.readUnsignedByte() != 0xF4) return false;
			if(bytes.readUnsignedByte() != 0x81) return false;
			if(bytes.readUnsignedByte() != 0x0F) return false;
			
			return true;
		}
		
		
		/**
		 * get simple sort function.sort by rules
		 * eg: getSortFunctionBy("pamra1.param2","param3") will get:
		 * function(obj1:Object,obj2:Object):int{
		 * 		if(obj1["param1"]["param2"] > obj2["param1"]["param2"]) return 1;
		 * 		if(obj2["param1"]["param2"] > obj1["param1"]["param2"]) return -1;
		 * 		if(obj1["param3"] > obj2["param3"]) return 1;
		 * 		if(obj2["pamra3"] > obj1["param3"]) return -1;
		 * 		return 0;
		 * }
		 * @param rules
		 * @return 
		 * 
		 */		
		public static function getSortFunctionBy(...arg):Function{
			
			return function(obj1:Object,obj2:Object):int{
				var rules:Array = arg.concat();
				var arr:Array;
				while(rules.length){
					var o1:Object = obj1;
					var o2:Object = obj2;
					arr = (rules.shift() as String).split(".");
					while(arr.length){
						var rule:String = arr.shift();
						o1 = o1[rule];
						o2 = o2[rule];
					}
					
					if(o1 > o2) return 1;
					if(o2 > o1) return -1;
				}
				return 0;
			}
		}
		/**
		 * return the scale width & height
		 * @param sou
		 * @param tar
		 * @return 
		 * 
		 */		
		public static function getScaleSize(sou:Point,tar:Point):Point{
			var per:Number=sou.x/sou.y;
			var result:Point;
			
			if((tar.x/tar.y)>per){
				return new Point(tar.y*per,tar.y);
			}else{
				return new Point(tar.x,tar.x/per);
			}
		}
		/**
		 * set the sou to target size
		 * @param sou
		 * @param tar
		 * @param autoPosition
		 * @param offset
		 * 
		 */		
		public static function setScaleSize(sou:DisplayObject,tar:Point,onlyZoomin:Boolean = false,autoPosition:Boolean=true,offset:Point=null):void{
			var souPoint:Point=new Point(sou.width,sou.height);
			var result:Point=getScaleSize(souPoint,tar);
			if(result.x > sou.width || result.y > sou.height){
				if(!onlyZoomin){
					sou.width=result.x;
					sou.height=result.y;
				}
			}else{
				sou.width=result.x;
				sou.height=result.y;
			}
			if(autoPosition){
				sou.x=(tar.x-sou.width) >> 1;
				sou.y=(tar.y-sou.height) >> 1;
				if(offset){
					sou.x += offset.x;
					sou.y += offset.y;
				}
			}
		}
		
		
		
		/**
		 * shuffle an array or a vector.<*>
		 * @param arr
		 * @return 
		 * 
		 */		
		public static function shuffleArray(arr:Array):Array{
			
			var result:Array = [];
			arr = arr.concat();
			while(arr.length){
				result.push(arr.splice(Random.integer(arr.length),1)[0]);
			}
			
			return result;
		}
		
		/**
		 * cut DisplayObject
		 * 
		 * @param source
		 * @param rect
		 * @return 
		 * 
		 */		
		public static function drawWith(source:DisplayObject,rect:Rectangle = null):BitmapData{
			if(!rect){
				rect = source.getRect(source);
			}
			var result:BitmapData=new BitmapData(rect.width,rect.height,true,0);
			var matrix:Matrix=new Matrix();
			matrix.tx=-rect.x;
			matrix.ty=-rect.y;
			result.draw(source,matrix);
			
			return result;
		}
		
		public static function drawInSize(source:DisplayObject, size:Point = null):BitmapData{
			if(!size) size = source.getRect(source).size;
			
			var result:BitmapData = new BitmapData(size.x, size.y, true,0);
			var matrix:Matrix = new Matrix();
			
			var perX:Number = size.x / source.width;
			var perY:Number = size.y / source.height;
			if(perX > perY){
				matrix.scale(perY, perY);
				
				matrix.tx = (size.x - source.width * perY) >> 1;
				
			}else{
				matrix.scale(perX, perX);
				
				matrix.ty = (size.y - source.height * perX) >> 1;
			}
			
			result.draw(source,matrix);
			
			return result;
		}
		
		/**
		 * 绘制给入DisplayObject实例,返回BitmapData,实现缩放
		 * @param source
		 * @param scale
		 * @return 
		 * 
		 */		
		public static function draw(source:DisplayObject,scale:Number=1, targetSize:Point = null):BitmapData{
			var rect:Rectangle=getRect(source,scale);
			
			var result:BitmapData;
			if(targetSize){
				result = new BitmapData(targetSize.x, targetSize.y, true, 0);
			}else{
				result = new BitmapData(rect.width,rect.height,true,0);
			}
			
			
			var matrix:Matrix=new Matrix();
			matrix.scale(scale,scale);
			matrix.tx=-rect.x;
			matrix.ty=-rect.y;
			result.draw(source,matrix);
			
			return result;
		}
		
		private static function getRect(source:DisplayObject,scale:Number=1,integer:Boolean=true):Rectangle{
			var result:Rectangle;
			if(source.parent){
				var p:Point=new Point(source.x,source.y);
				source.x=source.y=0;
				source.scaleX=source.scaleY=scale;
				result=source.getBounds(source.parent);
				source.x=p.x;
				source.y=p.y;
			}else{
				var tmp:Sprite=new Sprite();
				tmp.addChild(source);
				source.scaleX=source.scaleY=scale;
				result=source.getBounds(source.parent);
				tmp.removeChild(source);
				tmp=null;
			}
			source.scaleX=source.scaleY=1;
			
			if(integer){
				result.x=Math.floor(result.x);
				result.y=Math.floor(result.y);
				result.width=Math.ceil(result.width);
				result.height=Math.ceil(result.height);
			}
			
			return result;
		}
		
		/**
		 * 递归MovieClip实例跳转指定帧以便绘制
		 * @param source
		 * 
		 */		
		public static function allGotoFrame(source:MovieClip,frame:uint,ignore:MovieClip = null):void{
			var f:uint=frame%(source.totalFrames+1);
			if(!f) f=1;
			if(source != ignore){
				source.gotoAndStop(f);
			}
			
			for(var i:uint=0;i<source.numChildren;i++){
				if(source.getChildAt(i) is MovieClip){
					
					allGotoFrame(source.getChildAt(i) as MovieClip,frame,ignore);
				}
			}
		}
		
		/**
		 * 根据给入MovieClip实例获取该实例所占矩形区域,实现缩放,并取整
		 * @param source
		 * @param scale
		 * @param integer
		 * @return 
		 * 
		 */		
		public static function getMaxRectFromMC(source:MovieClip,scale:Number=1,integer:Boolean=true):Rectangle{
			allGotoFrame(source,1);
			
			var result:Rectangle=getRect(source,scale,integer);
			
			while(source.currentFrame<source.totalFrames){
				
				allGotoFrame(source,source.currentFrame+1);
				result=result.union(getRect(source,scale,integer));
				
			}
			
			allGotoFrame(source,1);
			return result;
		}
		
		
		/**
		 * 逐帧绘制给入MovieClip对象,返回Vector.<BitmapData>组,实现缩放
		 * @param source
		 * @param scale
		 * @return 
		 * 
		 */		
		public static function getBtimapFrames(source:MovieClip,scale:Number=1):Vector.<BitmapFrame>{
			
			var result:Vector.<BitmapFrame>=new Vector.<BitmapFrame>();
			var max:Rectangle=getMaxRectFromMC(source,scale);
			var rect:Rectangle;
			
			while(result.length < source.totalFrames){
				
				allGotoFrame(source,result.length+1);
				rect=getRect(source,scale);
				var data:BitmapFrame=new BitmapFrame(rect.width,rect.height,true,0);
				var matrix:Matrix=new Matrix();
				matrix.scale(scale,scale);
				matrix.tx=-rect.x;
				matrix.ty=-rect.y;
				data.draw(source,matrix);
				data.x = rect.x-max.x
				data.y = rect.y-max.y;
				result.push(data);
			}
			
			return result;
		}
		
		private static function getRectInParent(source:DisplayObject,scale:Number=1,integer:Boolean=true):Rectangle{
			var result:Rectangle;
			if(source.parent){
				source.scaleX=source.scaleY=scale;
				result=source.getBounds(source.parent);
			}else{
				var tmp:Sprite=new Sprite();
				tmp.addChild(source);
				source.scaleX=source.scaleY=scale;
				result=source.getBounds(source.parent);
				tmp.removeChild(source);
				tmp=null;
			}
			source.scaleX=source.scaleY=1;
			
			if(integer){
				result.x=Math.floor(result.x);
				result.y=Math.floor(result.y);
				result.width=Math.ceil(result.width)+1;
				result.height=Math.ceil(result.height)+1;
			}
			return result;
		}
		
		public static function storageAllBitmapDataToLocal(name:String):void{
			var maps:Vector.<String> = BitmapDataStorage.instance.getKeys();
			
			var options:PNGEncoderOptions = new PNGEncoderOptions(true);
			var bmp:BitmapData;
			var n:String;
			
			var list:Object = new Object();
			var b:ByteArray;
			var rect:Rectangle;
			while(maps.length){
				n = maps.pop();
				bmp = BitmapDataStorage.instance.getBitmapByKey(n);
				//saveFile(n, bytes);
				//list[n] = bmp.encode(bmp.rect,options);
				b = new ByteArray();
				rect = bmp.rect;
				if(bmp is BitmapFrame){
					rect.x = (bmp as BitmapFrame).x;
					rect.y = (bmp as BitmapFrame).y;
					b.writeBoolean(true);
				}else{
					b.writeBoolean(false);
				}
				b.writeObject(rect);
				b.writeBytes(bmp.getPixels(bmp.rect));
				list[n] = b;
			}
			
			var bytes:ByteArray = new ByteArray();
			bytes.writeObject(list);
			bytes.compress();
			saveFile(name, bytes);
		}
		
		public static const EXTENSION:String = ".los";
		
		public static function loadBitmapDataFromLocal(name:String):void{
			var file:File = File.applicationStorageDirectory.resolvePath(name + EXTENSION);
			file.addEventListener(Event.COMPLETE, function complete(evt:Event):void{
				var bytes:ByteArray = file.data;
				bytes.uncompress();
				var list:Object = bytes.readObject();
				
				loadFromList(list);
				file.removeEventListener(Event.COMPLETE, complete);
			});
			file.load();
			
		}
		
		private static function loadFromList(list:Object):void{
			
			var b:ByteArray;
			var isFrame:Boolean;
			var obj:Object;
			var r:Rectangle;
			var bmp:BitmapData;
			for(var i:String in list){
				b = list[i];
				isFrame = b.readBoolean();
				obj = b.readObject();
				r = new Rectangle(0,0,obj.width,obj.height);

				if(isFrame){
					bmp = new BitmapFrame(r.width, r.height);
					(bmp as BitmapFrame).x = obj.x;
					(bmp as BitmapFrame).y = obj.y;
				}else{
					bmp = new BitmapData(r.width, r.height);
				}
				
				bmp.setPixels(r,b);
				
				BitmapDataStorage.instance.put(i,bmp);
			}
		}
		public static function saveFile(name:String, bytes:ByteArray, ext:String = null):void{
			var stream:FileStream = new FileStream();
			var file:File = File.applicationStorageDirectory.resolvePath(name + ext ? ("." + ext) : EXTENSION);
			stream.openAsync(file,FileMode.WRITE);
			stream.writeBytes(bytes);
			stream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, function progress(evt:OutputProgressEvent):void{
				if(!evt.bytesPending){
					Tools.echo(name + "storage at " + file.nativePath);
					stream.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, progress);
					stream.close();
				}
			});
		}
		
		public static function loadFile(name:String, ext:String = null):ByteArray{
			var result:ByteArray = new ByteArray();
			
			var stream:FileStream = new FileStream();
			var file:File = File.applicationStorageDirectory.resolvePath(name + ext ? ("." + ext) : EXTENSION);
			stream.openAsync(file,FileMode.READ);
			stream.addEventListener(ProgressEvent.PROGRESS, function onProgress(evt:ProgressEvent):void{
				trace(evt.bytesLoaded, evt.bytesTotal);
				
				stream.readBytes(result);
				
				
				trace(result.length);
			});
			//stream.writeBytes(result);
			stream.addEventListener(OutputProgressEvent.OUTPUT_PROGRESS, function progress(evt:OutputProgressEvent):void{
				if(!evt.bytesPending){
					Tools.echo(name + "storage at " + file.nativePath);
					stream.removeEventListener(OutputProgressEvent.OUTPUT_PROGRESS, progress);
					stream.close();
				}
			});
			
			return result;
		}
		
		
		
		/**
		 * static method.return the number which between low - high
		 * 
		 * @param num
		 * @param low
		 * @param high
		 * @return 
		 * 
		 */
		public static function rangeNumber(num:Number,low:Number,high:Number):Number{
			return Math.max(low,Math.min(num,high));
		}
		
		
		
	}
}